package automaton;

import helpers.FileOperations;
import interfaces.Automaton;
import interfaces.State;
import interfaces.Transition;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import exceptions.UnknownInitialStateException;
import exceptions.UnknownTerminalStateException;

/**
 * @author TRAN NHAT QUANG
 *
 */

public class FA implements Automaton{	
	private final Map<State,Map<String,List<Transition<String>>>> finiteAutomata;
	private List<Transition<String>> transList;
	private List<String> labelList;

	
	/*public FA(DFA dfa)
	{
		this.finiteAutomata = new HashMap<State, Map<String,List<Transition<String>>>>();
		
		for (Map.Entry<State, Map<String, Transition<String>>> entry: dfa.getDFA().entrySet())
		{
			Map<String,List<Transition<String>>> mapValue = new HashMap<String,List<Transition<String>>>();
			for (Map.Entry<String,Transition<String>> en: entry.getValue().entrySet())
			{
				List<Transition<String>> lTrans = new ArrayList<>();
				
				if (en.getValue()!=null)
				{
					lTrans.add(en.getValue());
				
					mapValue.put(en.getKey(), lTrans);
					transList.add(en.getValue());
				
					if (!labelList.contains(en.getValue().label()))
						labelList.add(en.getValue().label());
				}
			}
			
			finiteAutomata.put(entry.getKey(), mapValue);
		}
	}*/
	
	public FA(@SuppressWarnings("unchecked") Transition<String>... transitions) throws UnknownInitialStateException, UnknownTerminalStateException
	{
		 this.finiteAutomata = new HashMap<State, Map<String,List<Transition<String>>>>();
		 transList = new ArrayList<>();
		 labelList = new ArrayList<>();
		 
		 for (Transition<String> transition : transitions) 
		 {
			 if (transition.source() != null && transition.target() != null)
			 {
				 transList.add(transition);
				 addState(transition.source());//add source of automata
				 addState(transition.target());// add target of automata
				 
				 Map<String, List<Transition<String>>> t = finiteAutomata.get(transition.source());
				 // put the transition
				 if (!t.containsKey(transition.label()))
					 t.put(transition.label(), new ArrayList<Transition<String>>());
				 t.get(transition.label()).add(transition);
				 
				 if (!labelList.contains(transition.label()))
					 labelList.add(transition.label());
			 }
			 else
				 System.out.println("Some state in null!!!");
		 }
		 
		 if(getInitStates().size() < 1)
			 throw new UnknownInitialStateException();
		 
		 if(getTermStates().size() < 1)
			 throw new UnknownTerminalStateException(this);
	}
	
	public FA(List<Transition<String>> transitions) throws UnknownInitialStateException, UnknownTerminalStateException
	{
		this.finiteAutomata = new HashMap<State, Map<String,List<Transition<String>>>>();
		 transList = new ArrayList<>();
		 labelList = new ArrayList<>();
		 
		 for (Transition<String> transition : transitions) 
		 {
			 transList.add(transition);
			 addState(transition.source());//add source of automata
			 addState(transition.target());// add target of automata
			 
			 Map<String, List<Transition<String>>> t = finiteAutomata.get(transition.source());
			 // put the transition
			 if (!t.containsKey(transition.label()))
				 t.put(transition.label(), new ArrayList<Transition<String>>());
			 t.get(transition.label()).add(transition);
			 
			 if (!labelList.contains(transition.label()))
				 labelList.add(transition.label());
		 }
		 
		 if(getInitStates().size() < 1)
			 throw new UnknownInitialStateException();
		 
		 if(getTermStates().size() < 1)
			 throw new UnknownTerminalStateException(this);
	}
	
	/**
	 * Add the new state into automaton
	 * @param s
	 */
	protected final void addState(State s){
		if(finiteAutomata!=null)
		{
			if(!finiteAutomata.containsKey(s))
			{
				finiteAutomata.put(s, new HashMap<String,List<Transition<String>>>());
				/*if (s.initial() ) 
					initState.add(s);
				if (s.terminal() )
					termState.add(s);*/
			}
		}
	}
	
	/**
	 * 
	 */
	public FA reduceInitState()
	{
		//System.out.println("Number of init: " + getInitStates().size());
		if (getInitStates().size() > 1)
		{
			State newInit = new StateImpl("sink", true, false);
			
			for (State s: getInitStates())
			{
				getTransitionsList().add(new TransitionImpl<String>(newInit, s, "empty"));
			}
			
			try {
				FA temp = new FA(getTransitions());
				for (State tempState: temp.getInitStates())
					if (tempState.label() != "sink")
						((StateImpl) tempState).setInitial(false);
				
				//System.out.println("Check reduce: "  +temp.getFA().get(newInit).size());
				
				return temp;
			} catch (UnknownInitialStateException
					| UnknownTerminalStateException e) {
				System.out.println(e.getMessage());
			}
		}
		//System.out.println("Number of init: " + getInitStates().size());
		return this;
	}
	
	/**
	 * Get the list of transitions
	 */
	@SuppressWarnings("unchecked")
	public Transition<String>[] getTransitions(){		
		Transition<String>[] tArray = new TransitionImpl[transList.size()];
		
		transList.toArray(tArray);
		
		return tArray;
	}
	
	public List<Transition<String>> getTransitionsList()
	{
		return transList;
	}
	
	/**
	 * Get the list of initial state
	 */
	public List<State> getInitStates()
	{
		List<State> rs = new ArrayList<>();
		
		for (State temp: getFA().keySet())
			try
			{
				if (temp.initial())
					rs.add(temp);
			}
			catch (Exception ex)
			{
				System.out.println("Can't get state's infomation: " + ex.getMessage());
			}
		
		return rs;
	}
	
	/**
	 * Get the list of terminal states
	 */
	public List<State> getTermStates()
	{
		List<State> rs = new ArrayList<>();
		
		for (State temp: getFA().keySet())
			try
			{
				if (temp.terminal())
				rs.add(temp);
			}
			catch (Exception ex)
			{
				System.out.println("Can't get state's infomation: " + ex.getMessage());
			}
		
		return rs;
	}
	
	/**
	 * Get the list of label
	 */
	public List<String> getLabels()
	{
		return labelList;
	}
	
	/**
	 * Get the automata
	 */
	public Map<State,Map<String,List<Transition<String>>>> getFA(){
		return finiteAutomata;
	}
	
	/**
	 * reversing state of an automata
	 */
	public void reverseStates()
	{		
		for (State s: getFA().keySet())
			if(s instanceof StateImpl)
				((StateImpl) s).reversed();
	}	
		
	/**
	 * Print the automaton in console
	 */
	public void printString() 
	{				
		for (State s: getFA().keySet())
		{
			System.out.print(s.label() + ": ");
			if (s.initial())
				System.out.print("Initial, ");
			if (s.terminal())
				System.out.print("Terminal");
			System.out.println();
		}
		
		for (Transition<String> tran: getTransitions())
			System.out.println(tran.source().label() + " --> " + tran.label() + " --> " + tran.target().label());
	}
	
	/**
	 * 
	 */
	public boolean checkFA()
	{
		List<State> init = new ArrayList<>();
		init.addAll(getInitStates());
		
		while (init.size() > 0)
		{
			List<State> temp = new ArrayList<>();
			for (State s: init)
			{
				if (s.terminal())
					return true;
				
				temp.addAll(getTarget(s));
			}
			
			init.clear();
			init.addAll(temp);
		}
		
		return false;
	}
	
	/**
	 * 
	 * @param s
	 * @return
	 */
	public List<State> getTarget(State s)
	{
		List<State> rs = new ArrayList<>();
		
		for(Map.Entry<State,Map<String,List<Transition<String>>>> entry: finiteAutomata.entrySet()){		
			if (entry.getKey() == s)
				for(Map.Entry<String, List<Transition<String>>> t : entry.getValue().entrySet()){
					for (Transition<String> tempTran: t.getValue())
						rs.add(tempTran.target());
				}			
		}
		/*
		for (Transition<String> t: transList)
			if (s == t.source())
				rs.add(t.target());
			*/
		return rs;
	}
	
	/**
	 * 
	 */
	public String toString()
	{
		String rs = "";
		rs += "//Finite Automata\n";
		
		for(Map.Entry<State,Map<String,List<Transition<String>>>> entry: finiteAutomata.entrySet())
		{
			int i=0;
			rs+=entry.getKey().label() + "\t|";
			
			if (entry.getKey().initial())
				rs+=" initial\t|";
			else
				rs+="|";
			
			if (entry.getKey().terminal())
				rs+=" terminal\t| ";
			else
				rs+="|";
			
			
			for(Map.Entry<String, List<Transition<String>>> t : entry.getValue().entrySet())
			{
				for (Transition<String> temp: t.getValue())
				{
					if (i > 0)
					rs+=",\t";
					rs+= t.getKey() + ":" + temp.target().label();
					i++;
				}
			}		
			
			rs+="\n";
		}
		
		return rs;
	}
	
	/**
	 * save automata to text file
	 */
	public void saveToFile()
	{		
		FileOperations.saveFile("data/automata.txt", this.toString());
	}
}
